package dgut.party.business.security.filter;

import dgut.party.business.dao.rbac.RoleRepository;
import dgut.party.business.dao.user.AccountRepository;
import dgut.party.business.entity.base.Account;
import dgut.party.business.security.UrlGrantedAuthority;
import dgut.party.business.security.UserPrincipal;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.json.JSONObject;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * token的校验 该类继承自BasicAuthenticationFilter，在doFilterInternal方法中，
 * 从http头的Authorization 项读取token数据，然后用Jwts包提供的方法校验token的合法性。
 * 如果校验通过，就认为这是一个取得授权的合法请求
 *
 * @author Mercy Wu(a3049) 2018年3月6日
 */
@Component
public class JWTAuthenticationFilter extends OncePerRequestFilter {

  AccountRepository accountRepo;

  RoleRepository roleRepo;

  JSONObject accountCache;

  public JWTAuthenticationFilter(AccountRepository accountRepository, RoleRepository roleRepository) {
    this.accountRepo = accountRepository;
    this.roleRepo = roleRepository;
  }

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                  FilterChain filterChain) throws ServletException, IOException, AccessDeniedException {
    String token = request.getHeader("Authentication");

    if (token == null || !token.startsWith("Bearer ")) {
      filterChain.doFilter(request, response);
      return;
    }
    UsernamePasswordAuthenticationToken authentication = getAuthentication(token);
    SecurityContextHolder.getContext().setAuthentication(authentication);
    filterChain.doFilter(request, response);
  }

  /**
   * 解析token中的用户信息，获得认证token
   * author: Mercy Wu(a3049)
   * 2018年3月6日 下午1:49:17
   *
   * @param request
   * @return
   */
  private UsernamePasswordAuthenticationToken getAuthentication(String token) throws AccessDeniedException {
    if (token != null) {

      Claims tokenClaim = Jwts.parser().setSigningKey("MyJwtSecret")
        .parseClaimsJws(token.replace("Bearer ", "")).getBody();
      String username = tokenClaim.getSubject();
      if (username != null) {
        Account account = null;
        List<Account> accounts = accountRepo.findByUsernameOrPhone(username, username);
        if (accounts.isEmpty() || accounts.size() > 1)
          throw new AccessDeniedException("您的账户不存在，请重新登录或联络管理员");
        account = accounts.get(0);

        if (!account.isEnabled()) {
          throw new AccessDeniedException("您的账户已被停用，请联络管理员");
        }
        Set<UrlGrantedAuthority> authorities = new HashSet<>();
        if (cacheExpire(username)) {
          account = accountRepo.findByUsernameJoinRoleAuthority(username).get(0);
          account.getRoles().forEach(role -> {
            role.getAuthorities().forEach(auth -> authorities
              .add(new UrlGrantedAuthority(auth.getHttpMethod(), auth.getUrl())));
          });
          updateAuthCache(username, authorities.stream().map(auth -> auth.getAuthority())
            .collect(Collectors.toSet()));
        } else {
          JSONObject cache = getAuthCache().getJSONObject(username);
          // 不限制http method类型
          cache.getJSONArray("auths")
            .forEach(auth -> authorities.add(new UrlGrantedAuthority(null, (String) auth)));
        }

        UserPrincipal principal = new UserPrincipal(account.getId(),
          account.getUsername(), account.getPassword(),
          true,
          true,
          true,
          true, authorities);
        return new UsernamePasswordAuthenticationToken(principal, null, authorities);
      }
      return null;
    }
    return null;
  }

  private JSONObject getAuthCache() {
    if (this.accountCache == null)
      this.accountCache = new JSONObject();
    return this.accountCache;
  }

  private boolean cacheExpire(String username) {
    if (!getAuthCache().has(username))
      return true;
    Date beginTime = (Date) (getAuthCache().getJSONObject(username).get("time"));
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(beginTime);
    calendar.add(Calendar.DAY_OF_YEAR, 1);
    return calendar.after(new Date());
  }

  private void updateAuthCache(String name, Set<String> auths) {
    JSONObject json = new JSONObject();
    json.put("time", new Date());
    json.put("auths", auths);
    getAuthCache().put(name, json);
  }
}
